#include <DataInputOutput.h>
#include <memory>
#include <stdlib.h>

#include <Logging.h>
using logging::Logger;
using logging::LoggerFactory;
using logging::Level;


namespace DataInputOutput{

DecoratorDataInput::DecoratorDataInput(SingleTransformationProcess* process, DataInput* dataInput) {
	this->dataInput = dataInput;
	this->singleTransformationProcess = process;
}

DataUnit* DecoratorDataInput::getNext() throw(EndOfDataException){
	DataUnit* aDataUnit = this->dataInput->getNext();
	DataUnit* transformedDataUnit = this->singleTransformationProcess->process(aDataUnit);
	delete aDataUnit;
	return transformedDataUnit;
}

DecoratorDataInput::~DecoratorDataInput() {

}
/************************************************************************/
DecoratorDataOutput::DecoratorDataOutput(SingleTransformationProcess* process, DataOutput* dataOutput) {
	this->dataOutput = dataOutput;
	this->singleTransformationProcess = process;
}

void DecoratorDataOutput::append(DataUnit* aDataUnit) {
	DataUnit* transformedDataUnit = this->singleTransformationProcess->process(aDataUnit);
	this->dataOutput->append(transformedDataUnit);
	delete aDataUnit;
}

DecoratorDataOutput::~DecoratorDataOutput() {

}

/************************************************************************/
AbstractBufferedDecorator::AbstractBufferedDecorator(BlockTransformationProcess* process, unsigned int bufferSize){
	this->blockTransformationProcess = process;
	this->bufferSize = bufferSize;
        this->initialBufferSize = bufferSize;
        this->mallocBufferSize = bufferSize;
	this->buffer = (DataUnit**) malloc(bufferSize * sizeof(DataUnit*));
	this->current = bufferSize;
}

void AbstractBufferedDecorator::prepareBuffer() throw(EndOfDataException){
	if (this->current >= this->bufferSize){
		workBuffer();
		this->current = 0;
	}
}

AbstractBufferedDecorator::~AbstractBufferedDecorator(){
	free(this->buffer);
	//free(this->blockTransformationProcess);
}

//**************************

BufferedDecoratorDataInput::BufferedDecoratorDataInput(BlockTransformationProcess* btfp, DataInput* du) :
		AbstractBufferedDecorator(btfp,BS_BUFFER_SIZE){
	this->dataInput = du;

}

void BufferedDecoratorDataInput::workBuffer() throw(EndOfDataException){
	unsigned int i=0;
	list<DataUnit*> dataUnitsList;
        
        // "moco" me fijo si en el proceso anterior se agrando el bloque, si es
        // así, restablezco el tamaño original.
        // Como solo realoqué abajo si era mayor, no es necesario realocar aca.
        // Se quedara más grande
        if (this->bufferSize != this->initialBufferSize) {
            this->bufferSize = this->initialBufferSize;
        }
        
	try{
		for (i=0; i < this->bufferSize; i++){
			dataUnitsList.push_back(this->dataInput->getNext());
//			this->buffer[i] = this->dataInput->getNext();
		}
	}catch (EndOfDataException& e){
		if (i == 0){
			//EOF
			throw e;
		}
		if (this->bufferSize != i){
			//no me alcanzó para llenar el buffer
			this->bufferSize = i;
		}
	}
	//Apply the transformation process to the units
	dataUnitsList = this->blockTransformationProcess->process(dataUnitsList);
	DataUnit* currentUnit;
        if (dataUnitsList.size() != this->bufferSize) {
            // solo realoco si lo que se necesita es mayor que lo ya alocado
            if (dataUnitsList.size() > this->mallocBufferSize) {
                this->buffer =  (DataUnit**) realloc(this->buffer, dataUnitsList.size() * sizeof(DataUnit*));
                this->mallocBufferSize = dataUnitsList.size();
            }
            this->bufferSize = dataUnitsList.size();
        }
	for (i=0; i < this->bufferSize; i++){
		currentUnit = dataUnitsList.front();
		this->buffer[i] = new CharDataUnit(currentUnit->asInt());
		dataUnitsList.pop_front();
	}
	delete currentUnit;
}

DataUnit* BufferedDecoratorDataInput::getNext() throw(EndOfDataException){
	prepareBuffer();
	DataUnit* retValue = this->buffer[this->current];
	this->current++;
	return retValue;
}

BufferedDecoratorDataInput::~BufferedDecoratorDataInput(){

}

//************************************

BufferedDecoratorDataOutput::BufferedDecoratorDataOutput(BlockTransformationProcess* btp, DataOutput* datao) :

                        //moco para arreglarlo momentaneamente
                        // el + 8 es porque el BufferedDecoratorDataInput agrega
                        // 8 elementos a cada bloque
                        AbstractBufferedDecorator(btp,BS_BUFFER_SIZE+8){
	this->dataOutput = datao;
	this->current = 0;
}

void BufferedDecoratorDataOutput::workBuffer() throw(EndOfDataException){
	list<DataUnit*> dataUnits;
	for (unsigned int i=0; i < this->bufferSize; i++){
		dataUnits.push_back(this->buffer[i]);
	}
	dataUnits = this->blockTransformationProcess->process(dataUnits);

	for (list<DataUnit*>::iterator it = dataUnits.begin();
			it != dataUnits.end(); it++) {
		this->dataOutput->append((*it));
	}
}

void BufferedDecoratorDataOutput::append(DataUnit* dataUnit){
	prepareBuffer();
	this->buffer[this->current] = dataUnit;
	this->current++;
}

BufferedDecoratorDataOutput::~BufferedDecoratorDataOutput(){
//	for (unsigned int i=0; i < this->current; i++) {
//		this->dataOutput->append(this->buffer[i]);
//	}
	list<DataUnit*> dataUnits;
	for (unsigned int i=0; i < this->current; i++){
		dataUnits.push_back(this->buffer[i]);
	}
	dataUnits = this->blockTransformationProcess->process(dataUnits);

	for (list<DataUnit*>::iterator it = dataUnits.begin();
			it != dataUnits.end(); it++) {
		this->dataOutput->append((*it));
	}
}

//**************************************
FileDataInput::FileDataInput(const char* fileRoute){
	this->file.open(fileRoute, ifstream::in);
	reloadBuffer();
}

void FileDataInput::reloadBuffer() {
	this->file.read(this->buffer, IN_BUFFER_SIZE);
	this->loaded = this->file.gcount();
	this->count = 0;
}

DataUnit* FileDataInput::getNext() throw(EndOfDataException){
	if (this->count == this->loaded) {
		if (this->loaded == IN_BUFFER_SIZE) {
			reloadBuffer();
			if (this->loaded == 0) throw EndOfDataException();
		}
		else throw EndOfDataException();
	}
	char data = this->buffer[this->count++];
	return new CharDataUnit(data);
}

FileDataInput::~FileDataInput(){
	this->file.close();
}
//****************************************
FileDataOutput::FileDataOutput(const char* fileRoute){
	this->file.open(fileRoute, ofstream::out);
}

void FileDataOutput::append(DataUnit* dataUnit){
	dataUnit->writeOn(this->file);
	delete dataUnit;
}

FileDataOutput::~FileDataOutput(){
	this->file.flush();
	this->file.close();
}

//****************************************
FileBitDataInput::FileBitDataInput(const char* fileName){
	this->inputFile = new ifstream(fileName, ifstream::in);
	reloadBuffer();
}

void FileBitDataInput::reloadBuffer() {
	this->bitCount = 0;
	this->byteCount = 0;
	this->inputFile->read(this->buffer, BIT_BUFFER_SIZE);
	this->loadedCount = this->inputFile->gcount();
}

bool FileBitDataInput::getNextBit() throw(EndOfDataException){
	if (this->bitCount == 8) {
		this->bitCount = 0;
		this->byteCount++;
	}
	if (this->byteCount == this->loadedCount) {
		if (this->loadedCount < BIT_BUFFER_SIZE) {
			throw EndOfDataException();
		} else {
			reloadBuffer();
		}
	}
	unsigned char bit = this->buffer[this->byteCount] >> 7;
	this->buffer[this->byteCount] <<= 1;

//	unsigned char retBit = this->buffer[this->byteCount] >> (8 - this->bitCount);
	this->bitCount++;
	return bit;
//	unsigned char searchedBit = 1;
//	for (unsigned int i = 0; i < this->bitCount; i++) {
//		searchedBit *= 2;
//	}
//	searchedBit = this->buffer[this->byteCount] & searchedBit;
//	this->bitCount++;
//	return searchedBit;
}

ifstream* FileBitDataInput::getFile(){
	return this->inputFile;
}

FileBitDataInput::~FileBitDataInput(){
	this->inputFile->close();
	delete this->inputFile;
}

//*****************************************
FileBitDataOutput::FileBitDataOutput(const char* fileName){
	this->outputFile = new ofstream(fileName, ofstream::out);
	this->byteCount = 0;
	this->bitCount = 0;
	for (int i = 0; i < BIT_BUFFER_SIZE; this->buffer[i++] = 0);
}

void FileBitDataOutput::writeBit(bool value){
	char bitInAChar = value ? 1 : 0;
	this->buffer[this->byteCount] = (this->buffer[this->byteCount] << 1) | bitInAChar;
	this->bitCount++;
	if (this->bitCount == 8) {
		this->bitCount = 0;
		this->byteCount++;
	}
	if (this->byteCount == BIT_BUFFER_SIZE) flush();
}

void FileBitDataOutput::flush() {
	if (this->byteCount == 0) return;
	this->outputFile->write(this->buffer, this->byteCount);
	this->outputFile->flush();
	this->byteCount = 0;
	this->bitCount = 0;
}

void FileBitDataOutput::complete(){
	if (this->byteCount != 0){
		unsigned int bitq = this->bitCount;
		while(bitq < 8){
			this->writeBit(0);
			bitq++;
		}
	}
	this->flush();
}

FileBitDataOutput::~FileBitDataOutput(){
	flush();
	this->outputFile->close();
	delete this->outputFile;
}

}
